#include <dt-bindings/gpio/gpio.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/ioctl.h>
#include <linux/uaccess.h>
#include <linux/string.h>
#include <linux/wait.h>
#include <linux/types.h>
#include <linux/proc_fs.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/err.h>
// 蔡工的案例 , 使用#DEVICE_ATTR 来实现 
#include <linux/workqueue.h>  //添加对应头文件

/**
 *目标：  通过属性节点方式实现点灯
 */


#define DEBUG_FLAG 1

#ifdef DEBUG_FLAG
	#define DBG_IO(fmt,arg...)   printk("<<-IO-DEBUG->> "fmt"\n",##arg)
#else
	#define DBG_IO(fmt,arg...)	NULL 
#endif


/**
 * 	//add
	led_ctrl {
		status = "okay";
		compatible = "rk3399_led2_green";
		gpio_led = <&gpio1 23 GPIO_ACTIVE_HIGH>; // GPIO1_C7 
		
	};

 */


//static struct delayed_work delay_wq; //定义一个delay_work结构体变量


// 单独编译内核  在代码根目录下面执行 ./kernel.sh 
// 烧 kernel下面的 boot.img 代码  parameter.txt要选上


static int gpio_led;  // gpio号
/*

static int gpio_val;

static void delay_workq_func(struct work_struct *work)  //定义你要延时执行的函数
{
    gpio_direction_output(gpio_led, gpio_val); //设置GPIO为高电平
  	printk("%s(), __%d__, led gpio_val = %d\n\n", __func__, __LINE__, gpio_val);
	gpio_val = !gpio_val; //GPIO电平值取反	
    schedule_delayed_work(&delay_wq, msecs_to_jiffies(1000)); //添加schedule_delayed_work之后每隔1秒执行一次
}

static void workqueue_init_led(void)
{
	INIT_DELAYED_WORK(&delay_wq, delay_workq_func); //初始化工作队列
	schedule_delayed_work(&delay_wq, msecs_to_jiffies(1000)); //添加到延时工作队列，这里延时1秒
}
static void workqueue_exit_led(void) 
{
	cancel_delayed_work_sync(&delay_wq);  //取消延时工作队列
} 
*/




static ssize_t gpio_led_show(struct device *dev,  //读函数
			struct device_attribute *attr, char *buf)
{
	return sprintf(buf, "%d\n", gpio_get_value(gpio_led)); //获取gpio的电平值，1为高电平，0为低电平
}

static ssize_t gpio_led_store(struct device *dev, struct device_attribute *attr,  //写函数
						const char *buf, size_t size)
{
	int ret;
	int val;

	ret = kstrtoint(buf, 10, &val); //输入一个字符然后转换成十进制整形数
	if (ret) {
		printk("%s, kstrtoint error return %d\n", __func__, ret);
		return -1;
	}

	if (val == 1) {
		//printk("%s, (%d), gpio pull up!\n", __func__, __LINE__);
		gpio_direction_output(gpio_led, val); //设置GPIO为高电平，亮绿灯
	} else if (val == 0) {
		//printk("%s, (%d), gpio pull up!\n", __func__, __LINE__);
		gpio_direction_output(gpio_led, val); //设置GPIO为低电平，亮红灯
	} else
		printk("I only support 0 or 1 to ctrl led\n");

	return size;
}

// 读函数，可以读取gpio口电平
// 写函数，操作gpio口
//static DEVICE_ATTR(gpio_led, 0664, gpio_led_show, gpio_led_store); //调用DEVICE_ATTR创建设备节点

static struct device_attribute io_control_attrs[] = {
	__ATTR(gpio_led, 0664, gpio_led_show, gpio_led_store),
};


// 第一章：驱动开发
static int io_control_probe(struct platform_device *pdev) //compatible的属性和dts的compatible一致，就会调用probe函数
{
	int i;
	int ret = 0; 
	struct device_node *of_node = pdev->dev.of_node;
	enum of_gpio_flags flags;
	int gpio_value;
	
    printk("%s(), __%d__ probe success!\n", __func__, __LINE__);

#if 1
	// gpio_led就是gpio号 ， flags  高电平有效还是低电平有效
	/**
	 * 如何理解这个参数： 
	 *  led_ctrl_node 可以理解为 根节点下led_ctrl 
	 *  "gpio_led"  属性名
	 *  0 ：   编号，节点中有n相同名字的属性的时候，这个就是索引，一般不用
        gpio_led = <&gpio4 RK_PC3 GPIO_ACTIVE_HIGH>;  //GPIO7_A2   -- led红灯控制
	 */
	gpio_led = of_get_named_gpio_flags(of_node, "gpio_led", 0, &flags); //解析dts的gpio
	printk("gpio is %d --\n", gpio_led);

	if (!gpio_is_valid(gpio_led)) { //判断GPIO是否合法能用, 如果gpio口被占用了，比如背光，不合理， requeset失败
		printk("gpio: %d is invalid\n", gpio_led);
	 	return -ENODEV;
	}else{
		gpio_value = (flags == GPIO_ACTIVE_HIGH)? 1:0;
	 	printk("gpio_value pin level is %d\n", gpio_value); 
	    if (gpio_request(gpio_led, "gpio_led")) { //申请GPIO口资源
		   printk("gpio_led: %d request failed!\n", gpio_led);
		   gpio_free(gpio_led); //如果申请失败，要释放GPIO的占用
		   return -ENODEV;
	    }
	    gpio_direction_output(gpio_led, !gpio_value); //设置GPIO初始电平为低电平
	}
  
#endif 




/**
   #DEVICE_ATTR 一个宏，用来创建设备节点， 定义在  include/linux/device.h 文件 
   #DEVICE_ATTR  内部 通过 #ATTR 宏 实现  在inlucde/linux/sysfs.h 中
   device_create_file 定义在 drivers/base/core.c 中 ，声明 include/linux/device.h 中 ， 0 表示创建设备节点成功
 **/	
// 创建设备节点


   /*
	*  如何使用：
	   1. #AttR(节点gpio号，节点权限默认，gpoi节点写函数  ，获取gpio口节点读函数 )
	*  2. 调用device_create_file 创建设备节点

	* int device_create_file(struct device *dev,const struct device_attribute *attr)
	*  attr:  dev_attr_gpio_led 是#ATTR 内部拼接的 dev_attr_xx 
 
	gpio_led 是gpio号，也是在sys/devices/platform/led_ctrl/ 创建的属性文件
	对gpio_led读写，调用store ,show 函数

  在用户空间操作节点
  insmod led_ctrl.ko以后会创建属性文件gpio_led
  
  sys/devices/platform/led_ctrl/gpiod_led 
  cat gpiod_led
  echo 1 > gpio_led

 
	*/
	//device_create_file(&pdev->dev, &dev_attr_gpio_led);
	for (i = 0; i < ARRAY_SIZE(io_control_attrs); i++) {
		ret = device_create_file(&pdev->dev, &io_control_attrs[i]);
		if (ret) {
			printk("failed to create sysfs file\n");
			break;
		}
	}
	
	
	//workqueue_init_led();


	return 0;
}


static int io_control_remove(struct platform_device *pdev)
{
    int i;
	gpio_free(gpio_led);


	for (i = 0; i < ARRAY_SIZE(io_control_attrs); i++) {
        device_remove_file(&pdev->dev, &io_control_attrs[i]);
	}	

	return 0;
}


static struct of_device_id io_control_match [] = {
	{.compatible = "rk3399_led2_green",}, //这里应该要跟dts的.compatible保持一致，我这里视频课程中没有写成一样，
        {},						//但他有个模糊匹配的功能，就是匹配相近的字段, 为了防止出错，大家最好写成跟dts一样的字段
	};

static struct platform_driver io_control_driver = {
        .driver         = {
                .name = "io_control",
				.owner = THIS_MODULE, 
                .of_match_table = io_control_match,
        },
        .probe          = io_control_probe,
	   .remove = io_control_remove,
};

static int io_control_init(void)
{


	int retval = 0;

	retval = platform_driver_register(&io_control_driver);	
	if (retval < 0) {
		printk(KERN_ERR "%s retval=%d\n", __func__, retval);
		return retval;
	}
	return retval;
}

static void io_control_exit(void)
{

	
	//workqueue_exit_led(); 
	
	platform_driver_unregister(&io_control_driver); //注销驱动
}


module_init(io_control_init); //模块加载函数, linux启动， insmod 驱动的时候 
module_exit(io_control_exit); //模块卸载函数

MODULE_AUTHOR("denganzhi, 2998677551@qq.com");   // 邮箱
MODULE_DESCRIPTION("Control Led Driver");    //作者
MODULE_LICENSE("GPL");       //驱动遵守的开源协议GPL
